"""
简化版 Excel 问答路由示例：

功能：
1. /v1/excel/upload  上传 Excel / CSV，缓存到内存
2. /v1/excel/ask     根据 Excel 数据 + 问题 调用 LLM 给出答案
3. /v1/excel/clear   清除某个 session 下的 Excel 缓存

注意：
- 这是示例代码，用来给 Codex / Cursor 参考结构和逻辑。
- 请不要直接覆盖你现有的 excel.py，而是对照合并、精简、补日志。
"""

import os
import tempfile
import time
import uuid
import logging
from typing import Any, Dict, List, Optional

import pandas as pd
from fastapi import (
    APIRouter,
    BackgroundTasks,
    File,
    Form,
    HTTPException,
    UploadFile,
)

from backend.common.config import config_manager
from backend.llm import llm_manager
from backend.excel import ExcelSessionRecord, InMemoryExcelSessionStore

logger = logging.getLogger("api.excel.simple")

router = APIRouter(
    # 等价于 /v1/excel
    prefix=f"/{config_manager.api.version}/excel",
    tags=["Excel简化示例"],
    responses={404: {"description": "Not found"}},
)

# ---------------------------
# 简单的会话缓存 & 参数限制
# ---------------------------

MAX_EXCEL_SESSIONS = 100
MAX_FILES_PER_SESSION = 3
MAX_PROMPT_COLUMNS = 20
MAX_DTYPE_COLUMNS = 20
MAX_SAMPLE_ROWS = 5
MAX_NUMERIC_STATS_COLUMNS = 8

excel_session_store = InMemoryExcelSessionStore(
    max_sessions=MAX_EXCEL_SESSIONS,
    max_files_per_session=MAX_FILES_PER_SESSION,
)


def _cleanup_records(records: List[ExcelSessionRecord]) -> None:
    """
    清理被驱逐的记录对应的临时文件
    """
    for record in records:
        temp_path = getattr(record, "temp_path", None)
        if temp_path and os.path.exists(temp_path):
            try:
                os.unlink(temp_path)
            except Exception as exc:
                logger.warning(
                    "删除临时文件失败",
                    extra={"temp_path": temp_path, "error": str(exc)},
                )


# ---------------------------
# Excel 管理类：保存 / 取出 / 生成上下文 / 调 LLM
# ---------------------------

class ExcelManager:
    @staticmethod
    def _safe_to_markdown(frame: pd.DataFrame, **kwargs) -> str:
        """
        优先用 DataFrame.to_markdown，如果环境缺少 tabulate，则回退到 to_string。
        """
        try:
            return frame.to_markdown(**kwargs)
        except Exception as exc:
            logger.warning("to_markdown 失败，已回退到 to_string: %s", str(exc))
            return frame.to_string(**kwargs)

    @staticmethod
    def save_excel_file(file: UploadFile, session_id: str) -> str:
        """
        保存 Excel/CSV 到临时文件 + 读成 DataFrame + 放入内存缓存。
        返回 file_id，给前端 / 其它接口使用。
        """
        try:
            # 1. 校验文件类型
            file_ext = file.filename.split(".")[-1].lower()
            if file_ext not in ("xlsx", "csv"):
                raise HTTPException(status_code=400, detail="只支持 .xlsx / .csv 文件")

            file_id = str(uuid.uuid4())

            # 2. 落地成临时文件
            with tempfile.NamedTemporaryFile(
                delete=False, suffix=f".{file_ext}"
            ) as tmp:
                tmp.write(file.file.read())
                tmp_path = tmp.name

            # 3. 读成 DataFrame
            if file_ext == "xlsx":
                df = pd.read_excel(tmp_path)
            else:
                df = pd.read_csv(tmp_path)

            # 4. 存入内存缓存
            record = ExcelSessionRecord(
                session_id=session_id,
                file_id=file_id,
                file_name=file.filename,
                temp_path=tmp_path,
                dataframe=df,
                created_at=time.time(),
            )
            evicted = excel_session_store.put(record)
            _cleanup_records(evicted)

            logger.info("Excel 上传成功", extra={"session_id": session_id, "file": file.filename})
            return file_id

        except HTTPException:
            # 直接往外抛
            raise
        except Exception as exc:
            logger.exception("保存 Excel 文件失败")
            raise HTTPException(status_code=500, detail=f"保存Excel文件失败: {exc}")

    @staticmethod
    def get_dataframe(session_id: str, file_id: str) -> ExcelSessionRecord:
        """
        从内存缓存中取出记录（带 DataFrame 和文件名）
        """
        record = excel_session_store.get(session_id, file_id)
        if record is None:
            raise HTTPException(status_code=404, detail="未找到指定的 Excel 文件")
        return record

    @staticmethod
    def build_table_context(
        df: pd.DataFrame,
        sheet: Optional[str] = None,
        columns: Optional[list] = None,
        filters: Optional[Dict[str, Any]] = None,
    ) -> str:
        """
        生成给 LLM 用的表格文本摘要（列名、类型、少量样本、数值统计）
        """
        try:
            # 列过滤
            if columns:
                valid_cols = [c for c in columns if c in df.columns]
                if valid_cols:
                    df = df[valid_cols]

            # 简单等号过滤
            if filters:
                for col, val in filters.items():
                    if col in df.columns:
                        df = df[df[col] == val]

            ctx_parts: List[str] = []

            if sheet:
                ctx_parts.append(f"工作表: {sheet}")

            ctx_parts.append(f"表格大小: {df.shape[0]} 行 × {df.shape[1]} 列")

            # 列名
            cols = list(df.columns)
            if len(cols) > MAX_PROMPT_COLUMNS:
                visible = ", ".join(cols[:MAX_PROMPT_COLUMNS])
                ctx_parts.append(
                    f"列名（前 {MAX_PROMPT_COLUMNS} 列，共 {len(cols)} 列）: {visible}"
                )
            else:
                ctx_parts.append("列名: " + ", ".join(cols))

            # 类型信息
            dtype_strs = [f"{c}: {df[c].dtype}" for c in df.columns]
            if len(dtype_strs) > MAX_DTYPE_COLUMNS:
                ctx_parts.append(
                    "数据类型（部分）: "
                    + ", ".join(dtype_strs[:MAX_DTYPE_COLUMNS])
                    + " 等"
                )
            else:
                ctx_parts.append("数据类型: " + ", ".join(dtype_strs))

            # 数据样本
            sample_n = min(MAX_SAMPLE_ROWS, len(df))
            if sample_n > 0:
                head_df = df.head(sample_n)
                ctx_parts.append("\n数据样本:\n")
                ctx_parts.append(ExcelManager._safe_to_markdown(head_df, index=False))

            # 数值统计
            num_cols = df.select_dtypes(include=["number"]).columns[:MAX_NUMERIC_STATS_COLUMNS]
            if len(num_cols) > 0:
                stats = df[num_cols].describe().round(2)
                ctx_parts.append("\n数值列统计:\n")
                ctx_parts.append(ExcelManager._safe_to_markdown(stats))

            return "\n".join(ctx_parts)

        except Exception as exc:
            logger.exception("生成表格上下文失败")
            return f"生成表格上下文时出错: {exc}"

    @staticmethod
    def ask_with_llm(df: pd.DataFrame, question: str, **ctx_kwargs: Any) -> str:
        """
        把表格摘要 + 用户问题 拼起来交给 LLM。
        """
        try:
            table_ctx = ExcelManager.build_table_context(df, **ctx_kwargs)

            prompt = f"""
你是一个数据分析助手，请根据下面的表格信息来回答用户问题。

【表格信息】
{table_ctx}

【用户问题】
{question}

回答要求：
1. 结论要基于表格数据本身，不要乱编。
2. 如果表格数据不够，说明原因，并给出建议要补什么数据。
3. 尽量用中文、分点说明，方便阅读。
""".strip()

            active_provider = config_manager.llm_providers.active_provider
            provider = llm_manager.get_provider(active_provider)

            # 注意：这里假设 provider 有 generate(prompt: str) -> str 的接口
            answer = provider.generate(prompt)
            return answer

        except Exception as exc:
            logger.exception("Excel 问答失败")
            return f"回答问题时出错: {exc}"

    @staticmethod
    def clear_session(session_id: str) -> None:
        """
        清除某个 session 里所有 Excel 缓存，并删除对应临时文件。
        """
        records = excel_session_store.clear_session(session_id)
        _cleanup_records(records)
        if records:
            logger.info(
                "清除了会话中的 Excel 文件",
                extra={"session_id": session_id, "count": len(records)},
            )


# ---------------------------
# 路由实现：上传 / 提问 / 清空
# ---------------------------

@router.post("/upload", summary="上传 Excel 文件")
async def upload_excel(
    file: UploadFile = File(...),
    session_id: str = Form(...),
):
    """
    由前端 Excel 状态栏调用：
    - file：.xlsx / .csv 文件
    - session_id：当前聊天会话 ID
    """
    file_id = ExcelManager.save_excel_file(file, session_id)
    return {
        "success": True,
        "file_id": file_id,
        "file_name": file.filename,
        "message": "文件上传成功",
    }


@router.post("/ask", summary="基于 Excel 提问")
async def ask_excel(
    session_id: str,
    file_id: str,
    question: str,
    sheet: Optional[str] = None,
    columns: Optional[str] = None,
    filters: Optional[str] = None,
):
    """
    前端在“对 Excel 提问”输入框回车时调用。
    参数通过 querystring 传递：
    - session_id
    - file_id
    - question
    - columns: 可选，逗号分隔的列名
    - filters: 可选，JSON 字符串，形如 {"列名": "值"}
    """
    # 解析 columns
    columns_list: Optional[List[str]] = None
    if columns:
        columns_list = [c.strip() for c in columns.split(",") if c.strip()]

    # 解析 filters
    filters_dict: Optional[Dict[str, Any]] = None
    if filters:
        try:
            import json

            filters_dict = json.loads(filters)
        except Exception:
            raise HTTPException(status_code=400, detail="filters 参数必须是合法 JSON")

    record = ExcelManager.get_dataframe(session_id, file_id)
    df = record.dataframe
    file_name = record.file_name

    answer = ExcelManager.ask_with_llm(
        df=df,
        question=question,
        sheet=sheet,
        columns=columns_list,
        filters=filters_dict,
    )

    # 生成一个简短预览（只取上下文前几行，前端可以不用）
    table_ctx = ExcelManager.build_table_context(
        df=df, sheet=sheet, columns=columns_list, filters=filters_dict
    )
    preview = "\n".join(table_ctx.splitlines()[:10])

    return {
        "success": True,
        "answer": answer,
        "preview": preview,
        "file_id": file_id,
        "file_name": file_name,
    }


@router.delete("/clear", summary="清除 Excel 会话")
async def clear_excel(session_id: str):
    """
    聊天页 Excel 状态栏上的“清除”按钮调用。
    """
    ExcelManager.clear_session(session_id)
    return {"success": True, "message": "Excel 文件已清除"}
